package com.pmoc.framework.core.config.driver.redis;

import com.alibaba.fastjson.JSON;
import com.pmoc.framework.core.client.CallBack;
import com.pmoc.framework.core.config.driver.DataClient;
import com.pmoc.framework.core.constrants.SystemConstrant;
import com.pmoc.framework.core.event.AddEvent;
import com.pmoc.framework.core.model.Entity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.*;

/**
 * redis实现的客户端
 *
 * @author humdeef
 * @summary RedisDriver
 * @since 2019-05-13 18:45
 */
@ConditionalOnBean(RedisConfig.class)
@Configuration
@Slf4j
public class RedisClient implements DataClient, ApplicationContextAware {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private ApplicationContext applicationContext;


    @Override
    public Boolean add(Entity entity) {
        log.info("add entity:{}", entity);
        Long aLong = redisTemplate.opsForList().rightPush(SystemConstrant.REDIS_A_LIST +
                        SystemConstrant.SEPARATOR + entity.getAppName(),
                JSON.toJSONString(entity));
        if (aLong != null) {
            //发布添加值的事件
            applicationContext.publishEvent(new AddEvent(entity));
        }
        return false;
    }

    @Override
    public Boolean add(Entity entity, CallBack callback) {
        try {
            add(entity);
            callback.onSuccess(entity, true, "成功");
        } catch (Exception e) {
            callback.onFail(entity, false, "失败", e);
        }
        return true;
    }

    @Override
    public Boolean migrate(Entity entity) {
        log.info("migrate entity :{}", entity);
        if (entity == null) {
            return false;
        }
        //将值添加到b链表中
        String bKey = SystemConstrant.REDIS_B_LIST +
                SystemConstrant.SEPARATOR + entity.getAppName();
        Long size = redisTemplate.opsForList().size(bKey);
        if (size == null) {
            size = 0L;
        }
        //新增
        if (size == 0) {
            String target = JSON.toJSONString(entity);
            Long aLong = redisTemplate.opsForList().rightPush(bKey, target);
            if (aLong != null) {
                log.info("成功添加到b 链表：{}", entity);
                removeAListData(entity.getAppName(), target);
            }
        } else {
            // 里面已经存在很多等待处理的值
            return migrateData(entity, size);
        }
        return true;
    }

    /**
     * 迁移值，将alist的值删除，添加到b链表里面
     *
     * @param entity
     * @param size
     * @return
     */
    private Boolean migrateData(Entity entity, long size) {
        String bKey = SystemConstrant.REDIS_B_LIST +
                SystemConstrant.SEPARATOR + entity.getAppName();
        Entity preNode = null;
        int order = 0;
        for (int i = 0; i < size; i++) {
            Object object = redisTemplate.opsForList().index(bKey, i);
            if (object == null) {
                log.warn("数据出错");
                return false;
            }
            Entity node = JSON.parseObject(object.toString(), Entity.class);
            if (entity.getTid().compareTo(node.getTid()) > 0) {
                if (order != 2) {
                    order = 1;
                    preNode = node;
                    continue;
                }
                break;
            } else if (entity.getTid().compareTo(node.getTid()) < 0) {
                if (order != 1) {
                    order = 2;
                    preNode = node;
                    continue;
                }
                break;
            } else {
                log.warn("value equal!!!!");
                removeAListData(entity.getAppName(), object.toString());
                return true;
            }
        }
        if (order == 1) {
            addValueToBList(entity, bKey, JSON.toJSONString(preNode), order);
        }
        if (order == 2) {
            addValueToBList(entity, bKey, JSON.toJSONString(preNode), order);
        }
        return true;
    }

    /**
     * 将值添加到b链表中
     *
     * @param entity 对象
     * @param bKey   b链表的key
     * @param node   基于操作的node
     * @param order  1 右边插入 2 左边插入
     */
    private void addValueToBList(Entity entity, String bKey, String node, int order) {
        Long aLong = null;
        String target = JSON.toJSONString(entity);
        if (order == 1) {
            aLong = redisTemplate.opsForList().rightPush(bKey, node, target);
        } else {
            aLong = redisTemplate.opsForList().leftPush(bKey, node, target);
        }
        if (aLong != null) {
            log.info("成功添加到b 链表：{}", entity);
            removeAListData(entity.getAppName(), target);
        }
    }

    /**
     * 移除a链表的值，如果移除失败，会有重试job进行处理
     * 此处以后可以做统计
     *
     * @param applicationName
     * @param node
     */
    private void removeAListData(String applicationName, String node) {
        String key = SystemConstrant.REDIS_A_LIST +
                SystemConstrant.SEPARATOR + applicationName;
        redisTemplate.opsForList().remove(key, 0, node);

    }

    /**
     * 移除b链表中的值
     *
     * @param key
     */
    private void removeBListData(String key) {
        Object o = redisTemplate.opsForList().leftPop(key);
        if (o == null) {
            log.warn("无法找到值....");
        }
        Entity entity = JSON.parseObject(o.toString(), Entity.class);
        if (entity != null) {
            log.info("消费了 {}", entity.getTid());
        }
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 从b链表中消费值
     *
     * @param appName
     */
    @Override
    public void consume(String appName) {
        String key = SystemConstrant.REDIS_B_LIST +
                SystemConstrant.SEPARATOR + appName;
        Long size = redisTemplate.opsForList().size(key);
        if (size == null || size == 0) {
            log.warn("无值需要消费");
            return;
        }
        if (size == 1) {
            removeBListData(key);
            return;
        }
        String firstValue = "";
        Entity preNode = null;
        List<String> deleteNodes = new ArrayList<String>(Math.toIntExact(size));
        for (int i = 0; i < size; i++) {
            Object o = redisTemplate.opsForList().index(key, i);
            if (o == null) {
                break;
            }
            if (i == 0) {
                firstValue = o.toString();

                if (firstValue != null) {
                    Entity e = JSON.parseObject(firstValue, Entity.class);
                    if (e != null && e.getTid() != null) {
                        deleteNodes.add(firstValue);
                        preNode = e;
                    }
                    continue;
                }
            }
            Entity node = JSON.parseObject(o.toString(), Entity.class);
            if (preNode != null && node != null && preNode.getTid() != null && node.getTid() != null) {
                if ((!preNode.getExistGap() && (node.getTid() - preNode.getTid()) == 1)
                        || node.getExistGap() && node.getTid() > preNode.getTid()) {
                    preNode = node;
                    deleteNodes.add(o.toString());
                    continue;
                }
            }
            break;
        }
        if (deleteNodes.size() > 0) {
            Set<Long> consumeIds = new HashSet<>(Math.toIntExact(size));
            for (String node : deleteNodes) {
                Long result = redisTemplate.opsForList().remove(key, 0, node);
                if (result != null && result > 0) {
                    Entity val = JSON.parseObject(node, Entity.class);
                    consumeIds.add(val.getTid());
                }
            }
            log.info("一次消费了...{}", Arrays.asList(consumeIds));
        }
    }

    @Override
    public void checkData(String appName) {
        String aKey = SystemConstrant.REDIS_A_LIST +
                SystemConstrant.SEPARATOR + appName;
        long size = redisTemplate.opsForList().size(aKey);
        if (size == 0) {
            return;
        }
        if (size > SystemConstrant.CHECK_DATA_LIMIT) {
            size = SystemConstrant.CHECK_DATA_LIMIT;
        }
        List<Object> range = redisTemplate.opsForList().range(aKey, 0, size);
        if (range == null || range.size() == 0) {
            return;
        }
        for (int i = 0; i < range.size(); i++) {
            Object o = range.get(i);
            if (o == null) {
                continue;
            }
            Entity entity = JSON.parseObject(o.toString(), Entity.class);
            if (entity != null && entity.getTid() != null) {
                migrate(entity);
            }

        }

    }
}
